package cn.mengfly.fxtest;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.io.File;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * @author Mengfly
 */
public class MainApplication extends Application {

    public static void main(String[] args) {
        launch(args);
        System.out.println();
    }

    @Override
    public void start(Stage primaryStage) {
        Platform.setImplicitExit(false);

        ObservableList<String> strings = FXCollections.observableArrayList(getApps());
        ListView<String> listView = new ListView<>(strings);
        listView.setCellFactory(param -> new ListCell<String>() {
            @Override
            protected void updateItem(String item, boolean empty) {
                if (empty) {
                    setText("");
                } else {
                    setText(item.substring(item.lastIndexOf(".") + 1));
                }
                super.updateItem(item, empty);
            }
        });

        Button button = new Button("Start");
        button.setOnAction(event -> {
            int selectedIndex = listView.getSelectionModel().getSelectedIndex();
            if (selectedIndex >= 0) {
                Platform.runLater(() -> invokeMainMethod(strings.get(selectedIndex)));
            }
        });

        Button refreshButton = new Button("Refresh");
        refreshButton.setOnAction(event -> {
            strings.clear();
            strings.addAll(getApps());
        });

        Separator sep = new Separator();
        ButtonBar buttonBar = new ButtonBar();
        buttonBar.getButtons().addAll(button, refreshButton);

        listView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);

        VBox box = new VBox();

        VBox.setVgrow(listView, Priority.ALWAYS);
        box.getChildren().addAll(listView, sep, buttonBar);
        box.setSpacing(10);
        box.setPadding(new Insets(10));

        Scene scene = new Scene(box);

        primaryStage.setOnCloseRequest(event -> {
            Platform.exit();
        });

        primaryStage.setScene(scene);
        primaryStage.setTitle("Select App");
        primaryStage.show();

    }

    private List<String> getApps() {
        return new ArrayList<>(getClassName("cn.mengfly.fxtest.app", true))
                .stream().filter(s -> {
                    try {
                        Class<?> aClass = Class.forName(s);
                        return Application.class.isAssignableFrom(aClass);
                    } catch (ClassNotFoundException e) {
                        return false;
                    }
                }).sorted().collect(Collectors.toList());
    }

    public static void invokeMainMethod(String className) {
        try {
            Class<?> clazz = Class.forName(className);
            Object obj = clazz.newInstance();

            Method method = clazz.getMethod("start", Stage.class);
            method.invoke(obj, new Stage());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取某包下的所有类
     *
     * @param packageName 包名
     * @param isRecursion 是否遍历子包
     * @return 类的完整名称
     */
    public static Set<String> getClassName(String packageName, boolean isRecursion) {
        Set<String> classNames = null;
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        String packagePath = packageName.replace(".", "/");

        URL url = loader.getResource(packagePath);
        if (url != null) {
            String protocol = url.getProtocol();
            if ("file".equals(protocol)) {
                classNames = getClassNameFromDir(url.getPath(), packageName, isRecursion);
            } else if ("jar".equals(protocol)) {
                JarFile jarFile = null;
                try {
                    jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (jarFile != null) {
                    classNames = getClassNameFromJar(jarFile.entries(), packageName, isRecursion);
                }
            }
        } else {
            classNames = getClassNameFromJars(((URLClassLoader) loader).getURLs(), packageName, isRecursion);
        }
        return classNames;
    }

    private static Set<String> getClassNameFromJars(URL[] urLs, String packageName, boolean isRecursion) {
        Set<String> classNames = new HashSet<>();
        for (URL urL : urLs) {
            String classPath = urL.getPath();
            if (classPath.endsWith("classes/")) {
                continue;
            }
            JarFile jarFile = null;
            try {
                jarFile = new JarFile(classPath.substring(classPath.indexOf("/")));
            } catch (Exception e) {
                e.printStackTrace();
            }

            if (jarFile != null) {
                classNames.addAll(getClassNameFromJar(jarFile.entries(), packageName, isRecursion));
            }
        }
        return classNames;
    }

    private static Set<String> getClassNameFromJar(Enumeration<JarEntry> entries, String packageName, boolean isRecursion) {
        Set<String> classNames = new HashSet<>();
        while (entries.hasMoreElements()) {
            JarEntry jarEntry = entries.nextElement();
            if (!jarEntry.isDirectory()) {
                String entryName = jarEntry.getName().replace("/", ".");
                if (entryName.endsWith(".class") && !entryName.contains("$")) {
                    entryName = entryName.replace(".class", "");
                    if (isRecursion) {
                        classNames.add(entryName);
                    } else if (!entryName.replace(packageName + ".", "").contains(".")) {
                        classNames.add(entryName);
                    }
                }
            }
        }
        return classNames;
    }

    /**
     * 从项目文件获取某报下所有类
     *
     * @param path        文件路径
     * @param packageName 包名
     * @param isRecursion 是否遍历自包
     * @return 类的完整名称
     */
    private static Set<String> getClassNameFromDir(String path, String packageName, boolean isRecursion) {
        Set<String> className = new HashSet<>();
        File file = new File(path);
        File[] files = file.listFiles();
        if (files == null) {
            return Collections.emptySet();
        }
        for (File childFile : files) {
            if (childFile.isDirectory()) {
                if (isRecursion) {
                    className.addAll(getClassNameFromDir(childFile.getPath(), packageName + "." + childFile.getName(), isRecursion));
                }
            } else {
                String fileName = childFile.getName();
                if (fileName.endsWith(".class") && !fileName.contains("$")) {
                    className.add(packageName + "." + fileName.replace(".class", ""));
                }
            }
        }
        return className;
    }
}
